/*++

Copyright (c) 1999 - 2000  Microsoft Corporation

Module Name:

    sampstrm.cpp 

Abstract:

    This module contains the implementation for a sample MSP stream class.

--*/

#include "stdafx.h"

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

CSampleMSPStream::CSampleMSPStream() : CMSPStream()
{
    LOG((MSP_TRACE, "CSampleMSPStream::CSampleMSPStream entered."));

    m_fTransportConfigured = FALSE;
    m_fTerminalConnected   = FALSE;
    m_DesiredGraphState    = State_Stopped;
    m_ActualGraphState     = State_Stopped;

    //
    // INSERT HERE: initialize other data members
    //

    LOG((MSP_TRACE, "CSampleMSPStream::CSampleMSPStream exited."));
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

CSampleMSPStream::~CSampleMSPStream()
{
    LOG((MSP_TRACE, "CSampleMSPStream::~CSampleMSPStream entered."));
    LOG((MSP_TRACE, "CSampleMSPStream::~CSampleMSPStream exited."));
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

void CSampleMSPStream::FinalRelease()
{
    LOG((MSP_TRACE, "CSampleMSPStream::FinalRelease entered."));

    //
    // At this point we should have no terminals selected, since
    // Shutdown is supposed to be called before we are destructed.
    //

    _ASSERTE( 0 == m_Terminals.GetSize() );

    if ( m_fTransportConfigured )
    {
        //
        // INSERT HERE: Remove our transport filters from the graph and
        // release them.
        //
    }

    //
    // Call the base class method to clean up everything else.
    //

    CMSPStream::FinalRelease();

    LOG((MSP_TRACE, "CSampleMSPStream::FinalRelease exited."));
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CSampleMSPStream::get_Name (
    OUT     BSTR *                  ppName
    )
{
    LOG((MSP_TRACE, "CSampleMSPStream::get_Name - enter"));

    //
    // Check argument.
    //

    if (!ppName)
    {
        LOG((MSP_TRACE, "CSampleMSPStream::get_Name - "
            "bad return pointer - returning E_POINTER"));

        return E_POINTER;
    }

    //
    // Decide what string to return based on which stream this is.
    //

    ULONG ulID;
    
    if ( m_Direction == TD_CAPTURE )
    {
        ulID = IDS_CAPTURE_STREAM;
    }
    else
    {
        ulID = IDS_RENDER_STREAM;
    }

    //
    // Get the string from the string table.
    //

    const int   ciAllocSize = 2048;
    WCHAR       wszName[ciAllocSize];

    int iReturn = LoadString( _Module.GetModuleInstance(),
                              ulID,
                              wszName,
                              ciAllocSize - 1 );

    if ( iReturn == 0 )
    {
        _ASSERTE( FALSE );
        
        *ppName = NULL;

        LOG((MSP_ERROR, "CSampleMSPStream::get_Name - "
            "LoadString failed - returning E_UNEXPECTED"));

        return E_UNEXPECTED;
    }

    //
    // Convert to a BSTR and return the BSTR.
    //

    *ppName = SysAllocString(wszName);

    if ( *ppName == NULL )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::get_Name - "
            "SysAllocString failed - returning E_OUTOFMEMORY"));

        return E_OUTOFMEMORY;
    }

    LOG((MSP_TRACE, "CSampleMSPStream::get_Name - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// This implementation, typical of the simplest MSPs, limits each stream to
// having at most one terminal selected at a time.
//

STDMETHODIMP CSampleMSPStream::SelectTerminal(
    IN      ITTerminal *            pTerminal
    )
{
    LOG((MSP_TRACE, "CSampleMSPStream::SelectTerminal - enter"));

    //
    // We are going to access the terminal list -- grab the lock
    //

    CLock lock(m_lock);

    //
    // Reject if we already have a terminal selected.
    //

    if ( 0 != m_Terminals.GetSize() )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::SelectTerminal - "
            "exit TAPI_E_MAXTERMINALS"));

        return TAPI_E_MAXTERMINALS;
    }

    //
    // Use base class method to add it to our list of terminals.
    //

    HRESULT hr = CMSPStream::SelectTerminal(pTerminal);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::SelectTerminal - "
            "base class method failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Re-pause or re-start the stream if needed.
    //

    if ( m_DesiredGraphState == State_Paused )
    {
        hr = PauseStream();
    }
    else if ( m_DesiredGraphState == State_Running )
    {
        hr = StartStream();
    }
    else
    {
        _ASSERTE( m_DesiredGraphState == State_Stopped );

        hr = S_OK;
    }

    if ( FAILED(hr) )
    {
        LOG((MSP_TRACE, "CSampleMSPStream::SelectTerminal - "
            "can't regain old graph state - unselecting terminal - "
            "exit 0x%08x", hr));

		//
		// Unselect it to undo all of the above.
		//

	    UnselectTerminal(pTerminal);

        return hr;
    }

    LOG((MSP_TRACE, "CSampleMSPStream::SelectTerminal - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CSampleMSPStream::UnselectTerminal (
        IN     ITTerminal *             pTerminal
        )
{
    LOG((MSP_TRACE, "CSampleMSPStream::UnselectTerminal - enter"));

    CLock lock(m_lock);

    //
    // Use base class method to remove terminal from our list of terminals.
    //
    // Note: the failure cases are messy here. In this sample implementation
    // we choose to return an error code but release our reference to the
    // terminal when disconnection fails. This is typically better than
    // keeping our reference to the terminal, which would cause us to leak
    // the terminal whenever disconnection fails.
    //

    HRESULT hr = CMSPStream::UnselectTerminal(pTerminal);

    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "CSampleMSPStream::UnselectTerminal - "
            "base class method failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Stop the graph and disconnect the terminal if this call had it
    // connected. (We need a stopped graph to disconnect properly, and
    // couldn't have started the graph if the terminal isn't connected.)
    //

    if ( m_fTerminalConnected )
    {
        //
        // At this point we need to make sure the stream is stopped.
        // We can't use our own StopStream method because it
        // (1) changes the desired graph state to Stopped and 
        // (2) does nothing if no terminal has been selected (which it now
        //     thinks is the case)
        //

        _ASSERTE( m_fTransportConfigured );

        //
        // Stop the stream via the base class method.
        //

        hr = CMSPStream::StopStream();

        m_ActualGraphState = State_Stopped;

        if ( FAILED(hr) )
        {
            LOG((MSP_ERROR, "CSampleMSPStream::UnselectTerminal - "
                "Stop failed - 0x%08x", hr));

            // don't return hr -- we really want to continue and
            // disconnect if we can!
        }

        //
        // Get the ITTerminalControl interface.
        //

        ITTerminalControl * pTerminalControl;

        hr = pTerminal->QueryInterface(IID_ITTerminalControl,
                                       (void **) &pTerminalControl);

        if ( FAILED(hr) )
        {
            LOG((MSP_ERROR, "CSampleMSPStream::UnselectTerminal - "
                "QI for ITTerminalControl failed - exit 0x%08x", hr));

            return hr;
        }

        //
        // Disconnect the terminal.
        //

        hr = pTerminalControl->DisconnectTerminal(m_pIGraphBuilder, 0);

        pTerminalControl->Release();

        m_fTerminalConnected = FALSE;

        if ( FAILED(hr) )
        {
            LOG((MSP_ERROR, "CSampleMSPStream::UnselectTerminal - "
                "DisconnectTerminal failed - exit 0x%08x", hr));

            return hr;
        }

        //
        // INSERT HERE: Additional disconnection may be needed, for example
        // for transport fitlers or transform filters that need to be
        // used for some terminals but not for others.
        //
    }

    LOG((MSP_TRACE, "CSampleMSPStream::UnselectTerminal - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CSampleMSPStream::StartStream (void)
{
    LOG((MSP_TRACE, "CSampleMSPStream::StartStream - enter"));

    CLock lock(m_lock);

    m_DesiredGraphState = State_Running;

    //
    // Can't start the stream if our transport filters are not configured.
    //

    if ( ! m_fTransportConfigured )
    {
        LOG((MSP_WARN, "CSampleMSPStream::PauseStream - "
            "transport not configured so nothing to do yet - exit S_OK"));

        return S_OK;
    }

    //
    // Can't start the stream if no terminal has been selected.
    //

    if ( 0 == m_Terminals.GetSize() )
    {
        LOG((MSP_WARN, "CSampleMSPStream::StartStream - "
            "no Terminal so nothing to do yet - exit S_OK"));

        return S_OK;
    }

    //
    // Connect the terminal. This does nothing if this call already
    // connected the terminal and fails if another call has the
    // terminal connected.
    //

    HRESULT hr;

    hr = ConnectTerminal(m_Terminals[0]);

    if ( FAILED(hr) )
    {
        FireEvent(CALL_TERMINAL_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
        FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);

        LOG((MSP_ERROR, "CSampleMSPStream::StartStream - "
            "our ConnectTerminal failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Run the stream via the base class method.
    //

    hr = CMSPStream::StartStream();

    if ( FAILED(hr) )
    {
        //
        // Failed to run -- tell the app.
        //

        FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);

        LOG((MSP_ERROR, "CSampleMSPStream::StartStream - "
            "Run failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Fire event if this just made us active.
    //

    if ( m_ActualGraphState != State_Running )
    {
        m_ActualGraphState = State_Running;

        HRESULT hr2 = FireEvent(CALL_STREAM_ACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);

        if ( FAILED(hr2) )
        {
            LOG((MSP_ERROR, "CSampleMSPStream::StartStream - "
                "FireEvent failed - exit 0x%08x", hr2));

            return hr2;
        }
    }

    LOG((MSP_TRACE, "CSampleMSPStream::StartStream - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CSampleMSPStream::PauseStream (void)
{
    LOG((MSP_TRACE, "CSampleMSPStream::PauseStream - enter"));

    CLock lock(m_lock);

    m_DesiredGraphState = State_Paused;

    //
    // Can't pause the stream if our transport filters are not configured.
    //

    if ( ! m_fTransportConfigured )
    {
        LOG((MSP_WARN, "CSampleMSPStream::PauseStream - "
            "transport not configured so nothing to do yet - exit S_OK"));

        return S_OK;
    }

    //
    // Can't pause the stream if no terminal has been selected.
    //

    if ( 0 == m_Terminals.GetSize() )
    {
        LOG((MSP_WARN, "CSampleMSPStream::PauseStream - "
            "no Terminal so nothing to do yet - exit S_OK"));

        return S_OK;
    }

    //
    // Connect the terminal. This does nothing if this call already
    // connected the terminal and fails if another call has the
    // terminal connected.
    //

    HRESULT hr;

    hr = ConnectTerminal(m_Terminals[0]);

    if ( FAILED(hr) )
    {
        FireEvent(CALL_TERMINAL_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
        FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);

        LOG((MSP_ERROR, "CSampleMSPStream::StartStream - "
            "our ConnectTerminal failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Pause the stream via the base class method.
    //

    hr = CMSPStream::PauseStream();

    if ( FAILED(hr) )
    {
        //
        // Failed to pause -- tell the app.
        //

        FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);

        LOG((MSP_ERROR, "CSampleMSPStream::PauseStream - "
            "Pause failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Fire event if this just made us inactive.
    //

    if ( m_ActualGraphState == State_Running )
    {
        HRESULT hr2 = FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);

        if ( FAILED(hr2) )
        {
            m_ActualGraphState = State_Paused;

            LOG((MSP_ERROR, "CSampleMSPStream::PauseStream - "
                "FireEvent failed - exit 0x%08x", hr2));

            return hr2;
        }
    }

    m_ActualGraphState = State_Paused;

    LOG((MSP_TRACE, "CSampleMSPStream::PauseStream - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CSampleMSPStream::StopStream (void)
{
    LOG((MSP_TRACE, "CSampleMSPStream::StopStream - enter"));

    CLock lock(m_lock);

    m_DesiredGraphState = State_Stopped;

    //
    // Nothing to do if our transport is not configured.
    //

    if ( ! m_fTransportConfigured )
    {
        LOG((MSP_WARN, "CSampleMSPStream::StopStream - "
            "transport not configured - exit S_OK"));

        return S_OK;
    }

    //
    // Nothing to do if no terminal has been selected.
    //

    if ( 0 == m_Terminals.GetSize() )
    {
        LOG((MSP_WARN, "CSampleMSPStream::StopStream - "
            "no Terminal - exit S_OK"));

        return S_OK;
    }

    //
    // Stop the stream via the base class method.
    //

    HRESULT hr;

    hr = CMSPStream::StopStream();

    if ( FAILED(hr) )
    {
        //
        // Failed to stop -- tell the app.
        //

        FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);

        m_DesiredGraphState = m_ActualGraphState;

        LOG((MSP_ERROR, "CSampleMSPStream::StopStream - "
            "Stop failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Fire event if this just made us inactive.
    //

    if ( m_ActualGraphState == State_Running )
    {
        HRESULT hr2 = FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);

        if ( FAILED(hr2) )
        {
            m_ActualGraphState = State_Stopped;

            LOG((MSP_ERROR, "CSampleMSPStream::StopStream - "
                "FireEvent failed - exit 0x%08x", hr2));

            return hr2;
        }
    }

    m_ActualGraphState = State_Stopped;

    LOG((MSP_TRACE, "CSampleMSPStream::StopStream - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// A real MSP would have some arguments to this method, which would then be
// used to configure transport filters. The types of information passed here
// vary widely. For example, some MSPs may need to configure their transport
// filters with a GUID that indicates which device to use for streaming. This
// info would be passed to the MSP from the TSP via TAPI 3.0 (see sampcall.cpp).
//

HRESULT CSampleMSPStream::ConfigureTransport(void)
{
    LOG((MSP_TRACE, "CSampleMSPStream::ConfigureTransport - enter"));

    CLock lock(m_lock);

    //
    // INSERT HERE:
    // * create the transport filter(s)
    // * configure the transport filter(s) based on passe-in info
    // * add filters to our graph
    // * if some transport filters always need to be connected together
    //   (irrespective of what terminals are used, etc.) then they can be
    //   connected now).
    //

    m_fTransportConfigured = TRUE;

    LOG((MSP_TRACE, "CSampleMSPStream::ConfigureTransport - exit S_OK"));

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Add the terminal to the graph and connect it to our
// filters, if it is not already in use.
//

HRESULT CSampleMSPStream::ConnectTerminal(ITTerminal * pTerminal)
{
    LOG((MSP_TRACE, "CSampleMSPStream::ConnectTerminal - enter"));

    //
    // Find out the terminal's internal state.
    //

    TERMINAL_STATE state;
    HRESULT hr;

    hr = pTerminal->get_State( &state );

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "get_State on terminal failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // If we've already connected the terminal on this stream, then
    // there is nothing for us to do. Just assert that the terminal
    // also thinks it's connected.
    //

    if ( m_fTerminalConnected )
    {
        _ASSERTE( state == TS_INUSE );

        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "terminal already connected on this stream - exit S_OK"));

        return S_OK;
    }

    //
    // Otherwise we need to connect the terminal on this call. If the
    // terminal is already connected on another call, we must fail. Note
    // that since we are making several calls on the terminal here, the
    // terminal could become connected on another call while we are
    // in the process of doing this. If this happens, the we will just fail
    // later.
    //

    if ( state == TS_INUSE )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "terminal in use - exit TAPI_E_TERMINALINUSE"));

        return TAPI_E_TERMINALINUSE;
    }

    //
    // Get the ITTerminalControl interface.
    //

    ITTerminalControl * pTerminalControl;

    hr = m_Terminals[0]->QueryInterface(IID_ITTerminalControl,
                                        (void **) &pTerminalControl);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "QI for ITTerminalControl failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Find out how many pins the terminal has. If not one then bail as
    // we have no idea what to do with multiple-pin terminals (we don't
    // expose any such terminals from this MSP).
    //

    DWORD dwNumPinsAvailable;

    hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
                                           0,
                                           &dwNumPinsAvailable,
                                           NULL);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "query for number of terminal pins failed - exit 0x%08x", hr));
        
        pTerminalControl->Release();

        return hr;
    }

    if ( 1 != dwNumPinsAvailable )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "unsupported number of terminal pins - exit E_FAIL"));

        pTerminalControl->Release();

        return E_FAIL;
    }

    //
    // Actually connect the terminal.
    //

    IPin * pTerminalPin;

    hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
                                           0,
                                           &dwNumPinsAvailable,
                                           &pTerminalPin);
    
    if ( FAILED(hr) )
    {
        pTerminalControl->Release();

        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "ConnectTerminal on terminal failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Make the connection between our filters and the terminal's pin.
    //

    hr = ConnectToTerminalPin(pTerminalPin);
    
    pTerminalPin->Release();

    if ( FAILED(hr) )
    {
        pTerminalControl->DisconnectTerminal(m_pIGraphBuilder, 0);

        pTerminalControl->Release();

        LOG((MSP_ERROR, "CSampleMSPStream::ConnectTerminal - "
            "ConnectToTerminalPin failed - exit 0x%08x", hr));

        return hr;
    }

    //
    // Now we are actually connected. Update our state and perform postconnection
    // (ignore postconnection error code).
    //

    m_fTerminalConnected  = TRUE;

    pTerminalControl->CompleteConnectTerminal();

    pTerminalControl->Release();

    LOG((MSP_TRACE, "CSampleMSPStream::ConnectTerminal - exit S_OK"));

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//

HRESULT CSampleMSPStream::ConnectToTerminalPin(IPin * pTerminalPin)
{
    LOG((MSP_TRACE, "CSampleMSPStream::ConnectToTerminalPin - enter"));

    //
    // INSERT HERE: Code to connect the terminal's pin to some pin on some
    // transport filter belonging to the stream. See the documentation on
    // DirectShow in the Windows SDK for help.
    //

    LOG((MSP_TRACE, "CSampleMSPStream::ConnectToTerminalPin - exit S_OK"));

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// ProcessGraphEvent
//
// Sends an event to the app when we get an event from the filter graph.
// Note: The DirectShow graph events that the MSP cares about will vary
// from MSP to MSP. Be sure to modify this code to correctly propagate the
// events you need, depending on what filters you are using.
//

HRESULT CSampleMSPStream::ProcessGraphEvent(
    IN  long lEventCode,
    IN  long lParam1,
    IN  long lParam2
    )
{
    LOG((MSP_EVENT, "CSampleMSPStream::ProcessGraphEvent - enter"));

    HRESULT        hr = S_OK;

    switch (lEventCode)
    {
    case EC_COMPLETE:
        
        hr = FireEvent(CALL_STREAM_INACTIVE, (HRESULT) lParam1, CALL_CAUSE_UNKNOWN);
        break;
    
    case EC_USERABORT:
        
        hr = FireEvent(CALL_STREAM_INACTIVE, S_OK, CALL_CAUSE_UNKNOWN);
        break;

    case EC_ERRORABORT:
    case EC_STREAM_ERROR_STOPPED:
    case EC_STREAM_ERROR_STILLPLAYING:
    case EC_ERROR_STILLPLAYING:

        hr = FireEvent(CALL_STREAM_FAIL, (HRESULT) lParam1, CALL_CAUSE_UNKNOWN);
        break;

    default:
        
        LOG((MSP_EVENT, "CSampleMSPStream::ProcessGraphEvent - "
            "ignoring event code %d", lEventCode));
        break;
    }

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "CSampleMSPStream::ProcessGraphEvent - "
            "FireEvent failed - exit 0x%08x", hr));

        return hr;
    }

    LOG((MSP_EVENT, "CSampleMSPStream::ProcessGraphEvent - exit S_OK"));

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// FireEvent
//
// Sends an event to the application.
//

HRESULT CSampleMSPStream::FireEvent(
    IN MSP_CALL_EVENT        type,
    IN HRESULT               hrError,
    IN MSP_CALL_EVENT_CAUSE  cause
    )                                          
{
    LOG((MSP_EVENT, "CSampleMSPStream::FireEvent - enter"));

    //
    // Create the event structure. 
    //

    //
    // Note: 
    //
    // MSPEVENTITEMs are allocated with AllocateEventItem and freed with 
    // FreeEventItem
    //
    // AllocateEventItem takes an optional argument that specifies how many 
    // extra bytes need to be allocated in addition to the size of MSPEVENTITEM
    // 

    MSPEVENTITEM * pEventItem = AllocateEventItem();

    if (pEventItem == NULL)
    {
        LOG((MSP_ERROR, "CSampleMSPStream::FireEvent - "
            "can't create MSPEVENTITEM structure - exit E_OUTOFMEMORY"));

        return E_OUTOFMEMORY;
    }

    //
    // Fill in the necessary fields for the event structure.
    //

    pEventItem->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);
    pEventItem->MSPEventInfo.Event  = ME_CALL_EVENT;

    ITTerminal * pTerminal = NULL;

    if ( 0 != m_Terminals.GetSize() )
    {
        _ASSERTE( 1 == m_Terminals.GetSize() );
        pTerminal = m_Terminals[0];
        pTerminal->AddRef();
    }

    ITStream * pStream = (ITStream *) this;
    pStream->AddRef();

    pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Type      = type;
    pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Cause     = cause;
    pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pStream   = pStream;
    pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pTerminal = pTerminal;
    pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.hrError   = hrError;


#ifdef MSPLOG
    //
    // Spew some debug output to indicate what this is.
    //

    char * pszType;
    DWORD dwLevel;

    switch (type)
    {
    case CALL_NEW_STREAM:
        pszType = "CALL_NEW_STREAM (unexpected)";
        dwLevel = MSP_ERROR;
        break;

    case CALL_STREAM_FAIL:
        pszType = "CALL_STREAM_FAIL";
        dwLevel = MSP_INFO;
        break;

    case CALL_TERMINAL_FAIL:
        pszType = "CALL_TERMINAL_FAIL";
        dwLevel = MSP_INFO;
        break;

    case CALL_STREAM_NOT_USED:
        pszType = "CALL_STREAM_NOT_USED (unexpected)";
        dwLevel = MSP_ERROR;
        break;

    case CALL_STREAM_ACTIVE:
        pszType = "CALL_STREAM_ACTIVE";
        dwLevel = MSP_INFO;
        break;

    case CALL_STREAM_INACTIVE:
        pszType = "CALL_STREAM_INACTIVE";
        dwLevel = MSP_INFO;
        break;

    default:
        pszType = "UNKNOWN EVENT TYPE";
        dwLevel = MSP_ERROR;
        break;
    }

    LOG((dwLevel, "CSampleMSPStream::FireEvent - "
                  "EVENT DUMP: type      = %s", pszType));
    LOG((dwLevel, "CSampleMSPStream::FireEvent - "
                  "EVENT DUMP: pStream   = %p", pStream));    
    LOG((dwLevel, "CSampleMSPStream::FireEvent - "
                  "EVENT DUMP: pTerminal = %p", pTerminal));    
    LOG((dwLevel, "CSampleMSPStream::FireEvent - "
                  "EVENT DUMP: hrError   = %08x", hrError));    

#endif // ifdef MSPLOG

    //
    // Send the event to the app.
    //

    HRESULT hr = m_pMSPCall->HandleStreamEvent(pEventItem);

    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "CSampleMSPStream::FireEvent - "
            "HandleStreamEvent failed - returning 0x%08x", hr));

        pStream->Release();
        pTerminal->Release();

        
        //
        // Note:
        //
        // MSPEVENTITEMs are allocated with AllocateEventItem and freed with 
        // FreeEventItem
        //

        FreeEventItem(pEventItem);
        pEventItem = NULL;

        return hr;
    }

    LOG((MSP_EVENT, "CSampleMSPStream::FireEvent - exit S_OK"));

    return S_OK;
}


// eof
